/*
 * Copyright (c) 2015, Peter Thorson. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the WebSocket++ Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

 /**
  * NOTES
  *
  * This example uses a number of standard classes through the websocketpp::lib
  * namespace. This is to allow easy switching between Boost, the C++11 STL, and 
  * the standalone Asio library. Your program need not use these namespaces if
  * you do not need this sort of flexibility.
  */

#include "main.h"
#include "card.h"
#include "gameroom.h"
#include "player.h"
#include "pool.h"
#include "config.h"

using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
using websocketpp::lib::bind;

// pull out the type of messages sent by our config
typedef websocketpp::config::asio::message_type::ptr message_ptr;
typedef websocketpp::lib::shared_ptr<websocketpp::lib::asio::ssl::context> context_ptr;

string err(string e, bool isModal = false){
    if(isModal) return "{\"type\":-1,\"msg\":\"" + e + "\"}";
    else return "{\"type\":-2,\"msg\":\"" + e + "\"}";
}

string suc(string e, bool isModal = false){
    if(isModal) return "{\"type\":99,\"msg\":\"" + e + "\"}";
    else return "{\"type\":100,\"msg\":\"" + e + "\"}";
}

inline void send(server *s, websocketpp::connection_hdl hdl, const string& msg){
    try{
        s->send(hdl, msg, websocketpp::frame::opcode::text);
    } catch (websocketpp::exception const & e) {
        std::cout << "Echo failed because: "
                    << "(" << e.what() << ")" << std::endl;
    }
}

void on_message(server *s, websocketpp::connection_hdl hdl, message_ptr msg)
{
    // std::cout << "on_message called with hdl: " << hdl.lock().get()
    //           << " and message: " << msg->get_payload()
    //           << std::endl;

    // try {
    //     s->send(hdl, msg->get_payload().c_str(), msg->get_payload().size(), websocketpp::frame::opcode::binary);
    // } catch (websocketpp::exception const & e) {
    //     std::cout << "Echo failed because: "
    //               << "(" << e.what() << ")" << std::endl;
    // }


    string request = msg->get_payload();

    Json::Value root;
    Json::Reader reader;
    reader.parse(request, root);

    Json::Value user = root[0];
    Json::Value req;
    string userid = user["uid"].asString();
    //cout<<user["uid"].asString()<<endl;
    if(userid != hdlPool[hdl]->getUid()){
        cout<<hdlPool[hdl]->getUid()<<endl;
        cout<<userid<<endl;
        send(s, hdl, err("UID ERR"));
        
        return;
    }

    Player* curPlayer = playerPool[userid];

    for(int i=1; i<root.size(); i++){
        req=root[i];
        enum ClientReq type = static_cast<ClientReq>(req["type"].asInt());
        cout<<type<<endl;
        switch(type){
            case CREATE:
            {
                cout<<"create"<<endl;
                if(curPlayer->getState() == PLAYING){
                    send(s, hdl, err("You are already in game."));
                    return;
                }
                GameRoom* room = new GameRoom(s);
                if(curPlayer->joinGame(room) != ErrorType::SUCCESS){
                    send(s, hdl, err("JOIN FAIL"));
                    delete room;
                    return;
                }
                break;
            }
            case JOIN:
            {
                cout<<"join"<<endl;
                string rid = req["roomid"].asString();
                cout<<rid<<endl;

                if(!roomPool.count(rid)){
                    send(s, hdl, err("Room Not Found!", true));
                    return;
                }

                if(curPlayer->joinGame(roomPool[rid]) == ErrorType::WRONG_STATE_GAME){
                    send(s, hdl, err("Game has begun!", true));
                    return;
                }
                break;
            }
            case EXIT:
            {
                if(curPlayer->exit() != ErrorType::SUCCESS){
                    send(s, hdl, err("You are not in lobby."));
                    return;
                }
                break;
            }
            case PLAYERSET:
            {
                cout<<"set"<<endl;
                //playerPool[userid]->setPlayer(req["set"]);
                if(curPlayer->setPlayer(req["set"]) == ErrorType::WRONG_ARG){
                    //send(s, hdl, err("Please Retry", true));
                    send(s, hdl, "{\"type\":-3}");
                    cout<<"seterr"<<endl;
                    return;
                }
                //else curPlayer->ready();
                break;
            }
            case READY:
                curPlayer->ready();
                break;
            case UNREADY:
                curPlayer->unready();
                break;
            case GO:
                cout<<"go"<<endl;
                curPlayer->getRoom()->playerGo(curPlayer->getUid());
                break;
            case RETURN:
                cout<<"return"<<endl;
                curPlayer->getRoom()->playerBack(curPlayer->getUid());
                break;
            case NEXTROUND:
                curPlayer->getRoom()->askForNewRound(hdl);
                break;
            case TEST:
                cout<<"hdl address is "<<hdl.lock()<<endl;
                cout<<"uid is "<<userid<<endl;
                cout<<"uid in player is "<<hdlPool[hdl]->getUid()<<endl;
                cout<<"player addr is "<<playerPool[userid]<<endl;
                cout<<"player is in room "<<playerPool[userid]->getRoom()->getId()<<endl;
                break;
            case VERSION:
                send(s, hdl, "{\"type\":101,\"version\":\"0.1\"}");
                break;
            case GETLOBBY:
                curPlayer->getRoom()->askForLobby(hdl);
                break;
        }

    }
    //cout<<root["type"].asString()<<endl;
    //cout<<request<<endl;


    // ostringstream oss;
    // oss << hdl.lock();
    // auto itr = rooms.find(oss.str());
    // if (itr != rooms.end()) {
    //     itr->second.StartGame();
    // }
    // else {
    //     auto temp = rooms.emplace(oss.str(), GameRoom(s, hdl, oss.str()));
    //     (temp.first->second).StartGame();
    // }
}

bool on_validate(server* s, websocketpp::connection_hdl hdl) {
    server::connection_ptr con = s->get_con_from_hdl(hdl);

    vector<string> uconfig = con->get_requested_subprotocols();
    if(uconfig.empty()) return false;
    string uid = uconfig[0];

    //cout<<uid<<endl;
    if(uid.length()>0){
        if(playerPool.count(uid) > 0){
            return false;
            //if(hdlPool.count(hdl)>0) return false;
            //playerPool[uid]->recovery(hdl);
        }else{
            //cout<<"new player "<<uid<<endl;
            Player* newPlayer = new Player(uid, hdl);
            playerPool[uid] = newPlayer;
            hdlPool[hdl] = newPlayer;
            // cout<<newPlayer<<endl;
            // cout<<playerPool[uid]<<endl;
            // cout<<hdlPool[hdl]<<endl;
            // cout<<hdlPool.count(hdl)<<endl;
            // cout<<newPlayer->getState()<<endl;
            // cout<<newPlayer->getUid()<<endl;
        }
        
        con->select_subprotocol(uid);
        cout<<uid<<"认证成功"<<endl;
        return true;
    }
    else return false;
}

void on_http(server* s, websocketpp::connection_hdl hdl) {
    server::connection_ptr con = s->get_con_from_hdl(hdl);
    
    con->set_body("404 Not Found");
    con->set_status(websocketpp::http::status_code::not_found);
}

void on_close(server* s, websocketpp::connection_hdl hdl){
    // cout<<hdl.lock()<<endl;
    // cout<<hdlPool[hdl]<<endl;
    // cout<<hdlPool[hdl]->getUid()<<endl;
    delete hdlPool[hdl];
}

void on_open(server* s, websocketpp::connection_hdl hdl){
    send(s, hdl, "{\"type\":101,\"version\":\"0.1\"}");
}

std::string get_password() {
    return "none";
}

// See https://wiki.mozilla.org/Security/Server_Side_TLS for more details about
// the TLS modes. The code below demonstrates how to implement both the modern
enum tls_mode {
    MOZILLA_INTERMEDIATE = 1,
    MOZILLA_MODERN = 2
};

context_ptr on_tls_init(tls_mode mode, websocketpp::connection_hdl hdl) {
    namespace asio = websocketpp::lib::asio;

    std::cout << "on_tls_init called with hdl: " << hdl.lock().get() << std::endl;
    std::cout << "using TLS mode: " << (mode == MOZILLA_MODERN ? "Mozilla Modern" : "Mozilla Intermediate") << std::endl;

    context_ptr ctx = websocketpp::lib::make_shared<asio::ssl::context>(asio::ssl::context::sslv23);

    try {
        if (mode == MOZILLA_MODERN) {
            // Modern disables TLSv1
            ctx->set_options(asio::ssl::context::default_workarounds |
                             asio::ssl::context::no_sslv2 |
                             asio::ssl::context::no_sslv3 |
                             asio::ssl::context::no_tlsv1 |
                             asio::ssl::context::single_dh_use);
        } else {
            ctx->set_options(asio::ssl::context::default_workarounds |
                             asio::ssl::context::no_sslv2 |
                             asio::ssl::context::no_sslv3 |
                             asio::ssl::context::single_dh_use);
        }
        //ctx->set_password_callback(bind(&get_password));
        ctx->use_certificate_chain_file(chain_file);
        ctx->use_private_key_file(key_file, asio::ssl::context::pem);
        
        // Example method of generating this file:
        // `openssl dhparam -out dh.pem 2048`
        // Mozilla Intermediate suggests 1024 as the minimum size to use
        // Mozilla Modern suggests 2048 as the minimum size to use.
        ctx->use_tmp_dh_file(dh_file);

        std::string ciphers;
        
        if (mode == MOZILLA_MODERN) {
            ciphers = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK";
        } else {
            ciphers = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA";
        }
        
        if (SSL_CTX_set_cipher_list(ctx->native_handle() , ciphers.c_str()) != 1) {
            std::cout << "Error setting cipher list" << std::endl;
        }
    } catch (std::exception& e) {
        std::cout << "Exception: " << e.what() << std::endl;
    }
    return ctx;
}

int main() {
    // Create a server endpoint
    server echo_server;

    boost::asio::io_context ioContext;
    // Initialize ASIO
    echo_server.init_asio(&ioContext);

    // Register our message handler
    echo_server.set_message_handler(bind(&on_message,&echo_server,::_1,::_2));
    echo_server.set_http_handler(bind(&on_http,&echo_server,::_1));
    echo_server.set_validate_handler(bind(&on_validate, &echo_server, _1));
    echo_server.set_tls_init_handler(bind(&on_tls_init,MOZILLA_INTERMEDIATE,::_1));
    echo_server.set_close_handler(bind(&on_close, &echo_server, _1));
    echo_server.set_open_handler(bind(&on_open, &echo_server, _1));

    echo_server.set_reuse_addr(true);
    // Listen on port 9002
    echo_server.listen(8443);

    // Start the server accept loop
    echo_server.start_accept();

    // Start the ASIO io_service run loop
    echo_server.run();


    return 0;
}
